#ifndef _SELECT_SERVER_H_
#define _SELECT_SERVER_H_

#include "MySock.hpp"
#include <sys/select.h>
#include <unistd.h>
#include <unordered_map>

const int NUM = (sizeof(fd_set) * 8);  // 定义fd_set最多接收的fd个数 - fd_set是一个位图
const int FD_NONE = -1;  // 设定fd的初始值为-1，表示此处fd空缺，可以填入或者跳过

class SelectServer
{
public:
    SelectServer(const uint16_t port = 8080)
    {
        _listenSock = QiHai::Sock::socket();
        QiHai::Sock::bind(_listenSock, port);  // 服务器默认绑定0.0.0.0 ip
        QiHai::Sock::listen(_listenSock);
        // 首先对_fdsArray进行初始化，好存放我们需要处理的fd，并且处理后的结果
        for (int i = 0; i < NUM; ++i) _fdsArray[i] = FD_NONE;
        _fdsArray[0] = _listenSock;  // 默认监听套接字为第一个

        // 英译汉服务
        _map["Hello"] = "你好\n";
        _map["word"] = "世界\n";
        _map["technology"] = "技术\n";
        _map["yes"] = "是的\n";
        _map["ok"] = "好\n";
        _map["no"] = "不\n";
    }

    void Start()
    {
        // 首先，将通过监听套接字获取链接视为一种IO资源，我们是读取其中的链接的
        fd_set rfds;
        while(true)
        {
            // 因为rfd为输入输出参数，所以每次循环需要对其进行更新
            FD_ZERO(&rfds);  // 将文件集清空 - 本质是一个位图
            // 每次需要重新将输入的fd_set位图结果进行更新
            int maxFd = _listenSock;
            for(int i = 0; i < NUM; ++i)
            {
                if (_fdsArray[i] != FD_NONE)
                {
                    FD_SET(_fdsArray[i], &rfds);
                    if (_fdsArray[i] > maxFd) maxFd = _fdsArray[i];
                } 
            }
            int n = select(maxFd + 1, &rfds, nullptr, nullptr, nullptr);  // 最后一个参数nullptr为阻塞进行处理
            switch(n)
            {
            case 0:
                // 表示当前链接的所有fd没有就绪的 - 还需要进行等待 但是阻塞等待的话就不存在了
                logMessage(DEBUG, "select timeout......");
                break;
            case -1:
                logMessage(WARNING, "select error:%d-%s", errno, strerror(errno));
                break;
            default:
                // 出现文件就绪成功 IO = 等待 + 拷贝，等待成功！
                HandlerEvent(rfds);                
                break;
            }
        }
    }

    ~SelectServer()
    {
        if (_listenSock >= 0) close(_listenSock);
    }
private:
    // 此时select负责的多个fd中存在就绪的了，需要对其进行处理
    void HandlerEvent(fd_set& rfds)
    {
        //明确rfds是一个输入输出型参数，此时是输出型参数，如果对应fd等待资源就绪，内核会进行一个设置
        for (int i = 0; i < NUM; ++i)
        {
            if (_fdsArray[i] == FD_NONE) continue;
            if (FD_ISSET(_fdsArray[i], &rfds))  // 表示此时对应的就绪了
            {
                if (_fdsArray[i] == _listenSock) Accepter();  // 表示连接服务
                else Recver(i); // 正常的读服务
            }
        }
    }

    void Accepter()
    {
        // 连接服务
        int sock = QiHai::Sock::accept(_listenSock);  // 此时是就绪的，不用阻塞进行等待了 debug模式里面会打印客户端ip和port
        int pos = 1;  //从1开始
        for (; pos < NUM; ++pos)
        {
            if (_fdsArray[pos] == FD_NONE) break;
        }

        if (pos == NUM)
        {
            // 此时select托管的fd满了，不好意思，只能抛弃此连接了
            close(sock);
            logMessage(WARNING, "accept error,_fdsArray overload......");
        }
        else _fdsArray[pos] = sock;
    }

    void Recver(int pos)
    {
        // 读取服务
        // 当前应该配合协议进行读取，但是当前为了实现简单没有对数据进行处理
        char buffer[1024];
        int n = recv(_fdsArray[pos], buffer, sizeof(buffer) - 1, 0);  // 应该也是无需阻塞等待，直接读取
        if (n > 0)
        {
            buffer[n] = '\0';
            logMessage(DEBUG, "fds[%d]# %s", pos, buffer);

            // 这里简单利用一下map实现一个web翻译，没有利用select监管对应sock是否为空
            std::string request;
            for (int i = 0; i < n; ++i)
            {
                if ((buffer[i] <= 'Z' && buffer[i] >= 'A') || (buffer[i] >= 'a' && buffer[i] <= 'z')) request += buffer[i];
            }
            auto res = _map.find(request);
            std::string response;
            if (res == _map.end()) response = "抱歉,此单词我还在学习\n";
            else response = res->second;
            send(_fdsArray[pos], response.c_str(), response.size(), 0);  // 阻塞发送
        }
        else if (n == 0){
            // 对方关闭连接
            close(_fdsArray[pos]);
            _fdsArray[pos] = FD_NONE;
            logMessage(WARNING, "fds[%d] close, me too ......", pos);
        }
        else{
            // 读取出现错误
            logMessage(ERROR, "recv error %d-%s", errno, strerror(errno));
            close(_fdsArray[pos]);
            _fdsArray[pos] = FD_NONE;
        }
    }
private:
    int _listenSock;
    int _fdsArray[NUM];
    std::unordered_map<std::string, std::string> _map;
};



#endif